package inglar.autotc.client.ui;


import inglar.autotc.client.Utilities;
import inglar.autotc.shared.Values;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DoubleBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IntegerBox;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RadioButton;

public class DialogBoxEditNodeQDisc extends DialogBox {
	private VerticalPanel contentPanel;
	
	public TextBox txtbxParent;
	public TextBox txtbxHandle;
	private HorizontalPanel panelOptions;
	public ListBox listQDisc;
	public Label lblQdiscName;
	public HorizontalPanel panelPfifoFast;
	public HorizontalPanel panelPfifo;
	public HorizontalPanel panelBfifo;
	public HorizontalPanel panelSfq;
	public HorizontalPanel panelTbf;
	public HorizontalPanel panelPrio;
	public HorizontalPanel panelHtb;
	public DoubleBox dblbxPfifoLimit; // not required, may be 0
	public DoubleBox dblbxBfifoLimit; // not required, may be 0
	public IntegerBox intbxSfqLimit; // not required, minimum is 2, must not have suffix, must be integer
	public IntegerBox intbxSfqPerturb; // not required, may be 0, must not have suffix, must be integer
	public DoubleBox dblbxSfqQuantum; // not required
	public DoubleBox dblbxTbfRate; // required, minimum is 24bit (3bps), must have suffix
	public DoubleBox dblbxTbfBuffer; // required, minimum is 0.000062mbit (~8b) // XXX tbf burst has no 'cell' option
	public DoubleBox dblbxTbfLimit;  // required if no "latency", minimum is 0.000008mbit (1b)
	public DoubleBox dblbxTbfLatency; // required if no "limit", minimum is 1.0 (~0.000002s)
	public DoubleBox dblbxTbfMpu; // not required, may be 0, maximum is 8b, must not be suffixed by 'bit' (though kbit and mbit works)
//	public DoubleBox dblbxTbfPeakrate; // XXX can't make peakrate, mtu work in cmd line
//	public DoubleBox dblbxTbfMtu;
	public RadioButton rdbtnTbfLimit;
	public RadioButton rdbtnTbfLatency;
	public IntegerBox intbxPrioBands; // not required, must be at least 1, default is 3
	public TextBox txtbxPrioMap; // not required, must have 16 csv's
	public TextBox txtbxHtbDefault; // not required, must be a valid minor-id

	private NodeQDisc targetQdisc;

	public DialogBoxEditNodeQDisc() {
		setHTML("QDisc Editor");
		setGlassEnabled(false);
		setModal(true);
		
		contentPanel = new VerticalPanel();
		contentPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		setWidget(contentPanel);
		contentPanel.setSize("100%", "100%");
		
		FlexTable tblNodeDesc = new FlexTable();
		contentPanel.add(tblNodeDesc);
		
		Label lblParent = new Label("Parent:");
		tblNodeDesc.setWidget(0, 0, lblParent);
		
		txtbxParent = new TextBox();
		txtbxParent.setText("Parent");
		tblNodeDesc.setWidget(0, 1, txtbxParent);
		
		Label lblHandle = new Label("Handle:");
		tblNodeDesc.setWidget(0, 2, lblHandle);
//		lblHandle.setTitle("");
		
		txtbxHandle = new TextBox();
		txtbxHandle.setText("Flow ID");
		tblNodeDesc.setWidget(0, 3, txtbxHandle);
		
		Label lblQdisc = new Label("QDisc:");
		tblNodeDesc.setWidget(1, 0, lblQdisc);
		
		HorizontalPanel panel1 = new HorizontalPanel();
		tblNodeDesc.setWidget(1, 1, panel1);
		tblNodeDesc.getFlexCellFormatter().setColSpan(1, 1, 3);
		panel1.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
		
		listQDisc = new ListBox();
		panel1.add(listQDisc);
		for(int i = 0; i < NodeQDisc.qdiscs_classless.length; i++) {
			listQDisc.addItem(NodeQDisc.qdiscs_classless[i]);
		}
		for(int i = 0; i < NodeQDisc.qdiscs_classful.length; i++) {
			listQDisc.addItem(NodeQDisc.qdiscs_classful[i]);
		}
		listQDisc.setSelectedIndex(NodeQDisc.qdiscs_classless.length - 1);
		listQDisc.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				String q = listQDisc.getItemText(listQDisc.getSelectedIndex());
				setQDisc(q);
			}
		});
		
		lblQdiscName = new Label("");
		panel1.add(lblQdiscName);
		lblQdiscName.getElement().setAttribute("style", "margin-left: 10px;");
		
		panelOptions = new HorizontalPanel();
		contentPanel.add(panelOptions);
		panelOptions.getElement().setAttribute("style", "margin-top: 5px; margin-bottom: 5px;");
		
		HorizontalPanel panel2 = new HorizontalPanel();
		contentPanel.add(panel2);
		
		Button btnApply = new Button("Apply");
		panel2.add(btnApply);
		btnApply.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if(apply()) {
					targetQdisc.tsRef.setTreeSaved(false);
					hide();
				}
			}
		});
		btnApply.getElement().setAttribute("style", "margin-left: 5px; margin-right: 5px;");
		
		Button btnCancel = new Button("Cancel");
		panel2.add(btnCancel);
		btnCancel.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				hide(); // end the popup
			}
		});
		btnCancel.getElement().setAttribute("style", "margin-left: 5px; margin-right: 5px;");
		
		// pfifo_fast options
		panelPfifoFast = new HorizontalPanel();
		panelPfifoFast.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		panelPfifoFast.add(new HTML("<i>You cannot configure a pfifo_fast qdisc.</i>"));
		
		// pfifo options
		panelPfifo = new HorizontalPanel();
		panelPfifo.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		FlexTable table1 = new FlexTable();
		panelPfifo.add(table1);
		Label lblPfifoLimit = new Label("limit:");
		table1.setWidget(0, 0, lblPfifoLimit);
		lblPfifoLimit.setTitle("This sets the maximum number of packets this QDisc's queue can hold.");
		dblbxPfifoLimit = new DoubleBox();
		table1.setWidget(0, 1, dblbxPfifoLimit);
		table1.setWidget(0, 2, new Label("packets"));
		
		// bfifo options
		panelBfifo = new HorizontalPanel();
		panelBfifo.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		FlexTable table2 = new FlexTable();
		panelBfifo.add(table2);
		Label lblBfifoLimit = new Label("limit:");
		table2.setWidget(0, 0, lblBfifoLimit);
		lblBfifoLimit.setTitle("This sets the maximum amount of data this QDisc's queue can hold.");
		dblbxBfifoLimit = new DoubleBox();
		table2.setWidget(0, 1, dblbxBfifoLimit);
		table2.setWidget(0, 2, new Label("bytes"));
		
		// sfq options
		panelSfq = new HorizontalPanel();
		panelSfq.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		FlexTable table3 = new FlexTable();
		panelSfq.add(table3);
		Label lblSfqLimit = new Label("limit:");
		table3.setWidget(0, 0, lblSfqLimit);
		lblSfqLimit.setTitle("This sets the maximum number of packets this QDisc's queue can hold.");
		intbxSfqLimit = new IntegerBox();
		table3.setWidget(0, 1, intbxSfqLimit);
		table3.setWidget(0, 2, new Label("packets"));
		Label lblSfqPerturb = new Label("perturb:");
		table3.setWidget(1, 0, lblSfqPerturb);
		lblSfqPerturb.setTitle("This sets how frequent the QDisc will re-hash packets into hashbuckets to achieve fairness. Recommended values are 10 and above.");
		intbxSfqPerturb = new IntegerBox();
		table3.setWidget(1, 1, intbxSfqPerturb);
		table3.setWidget(1, 2, new Label("secs"));
		Label lblSfqQuantum = new Label("quantum:");
		table3.setWidget(2, 0, lblSfqQuantum);
		lblSfqQuantum.setTitle("This sets the amount of data for a hashbucket.");
		dblbxSfqQuantum = new DoubleBox();
		table3.setWidget(2, 1, dblbxSfqQuantum);
		table3.setWidget(2, 2, new Label("bytes"));
		
		// tbf options
		panelTbf = new HorizontalPanel();
		panelTbf.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		FlexTable table4 = new FlexTable();
		panelTbf.add(table4);
		Label lblTbfRate = new Label("rate:");
		table4.setWidget(0, 0, lblTbfRate);
		lblTbfRate.setTitle("This sets how fast the qdisc sends packets. This field is required.");
		dblbxTbfRate = new DoubleBox();
		table4.setWidget(0, 1, dblbxTbfRate);
		table4.setWidget(0, 2, new Label("bytes per second"));
		Label lblTbfBuffer = new Label("buffer:");
		table4.setWidget(1, 0, lblTbfBuffer);
		lblTbfBuffer.setTitle("This sets the maximum number of tokens (in bytes) available to send packets. This field is required.");
		dblbxTbfBuffer = new DoubleBox();
		table4.setWidget(1, 1, dblbxTbfBuffer);
		table4.setWidget(1, 2, new Label("bytes"));
		rdbtnTbfLimit = new RadioButton("tbf-1", "limit:");
		table4.setWidget(2, 0, rdbtnTbfLimit);
		rdbtnTbfLimit.setTitle("This sets the amount of packets (in bytes) that can be queued waiting for tokens to become available. This field is required in the absence of the 'latency' parameter.");
		dblbxTbfLimit = new DoubleBox();
		table4.setWidget(2, 1, dblbxTbfLimit);
		table4.setWidget(2, 2, new Label("bytes"));
		rdbtnTbfLatency = new RadioButton("tbf-1", "latency:");
		table4.setWidget(3, 0, rdbtnTbfLatency);
		rdbtnTbfLatency.setTitle("This specifies the maximum amount of time a packet can sit in the tbf. This field is required in the absence of the 'limit' parameter.");
		dblbxTbfLatency = new DoubleBox();
		table4.setWidget(3, 1, dblbxTbfLatency);
		table4.setWidget(3, 2, new HTML("&mu;secs"));
		Label lblTbfMpu = new Label("MPU:");
		table4.setWidget(4, 0, lblTbfMpu);
		lblTbfMpu.setTitle("Minimum Packet Unit. This sets the minimum token usage of a packet. A packet that is sent removes at least this many bytes from the bucket.");
		dblbxTbfMpu = new DoubleBox();
		table4.setWidget(4, 1, dblbxTbfMpu);
		table4.setWidget(4, 2, new Label("bytes"));
		
		// prio options
		panelPrio = new HorizontalPanel();
		panelPrio.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		FlexTable table5 = new FlexTable();
		panelPrio.add(table5);
		Label lblPrioBands = new Label("bands:");
		table5.setWidget(0, 0, lblPrioBands);
		lblPrioBands.setTitle("This sets the number of bands (sub-queues). If changed from the default of 3, priomap must be updated as well.");
		intbxPrioBands = new IntegerBox();
		table5.setWidget(0, 1, intbxPrioBands);
		Label lblPrioMap = new Label("priomap:");
		table5.setWidget(1, 0, lblPrioMap);
		lblPrioMap.setTitle("This sets the list of 12 comma-separated values that determines which band (denoted by a minor-id) will a packet be sent to based on that packet's TOS field. See the user manual for details.");
		txtbxPrioMap = new TextBox();
		table5.setWidget(1, 1, txtbxPrioMap);
		txtbxPrioMap.setVisibleLength(33);
		
		// htb options
		panelHtb = new HorizontalPanel();
		panelHtb.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
		FlexTable table6 = new FlexTable();
		panelHtb.add(table6);
		Label lblHtbDefault = new Label("default:");
		table6.setWidget(0, 0, lblHtbDefault);
		lblHtbDefault.setTitle("This sets the child class where unfiltered packets are sent.");
		txtbxHtbDefault = new TextBox();
		table6.setWidget(0, 1, txtbxHtbDefault);
		table6.setWidget(0, 2, new Label("minor-id"));

		setQDisc(listQDisc.getItemText(listQDisc.getSelectedIndex()));
	}
	
	private void setQDisc(String q) {
		panelOptions.clear();
		if(q.equals("pfifo_fast")) {
			panelOptions.add(panelPfifoFast);
			lblQdiscName.setText("Default QDISC");
		} else if(q.equals("pfifo")) {
			panelOptions.add(panelPfifo);
			lblQdiscName.setText("Packet-limited First In, First Out Queue");
		} else if(q.equals("bfifo")) {
			panelOptions.add(panelBfifo);
			lblQdiscName.setText("Byte-limited First In, First Out Queue");
		} else if(q.equals("sfq")) {
			panelOptions.add(panelSfq);
			lblQdiscName.setText("Stochastic Fairness Queueing");
		} else if(q.equals("tbf")) {
			panelOptions.add(panelTbf);
			lblQdiscName.setText("Token Bucket Filter");
		} else if(q.equals("prio")) {
			panelOptions.add(panelPrio);
			lblQdiscName.setText("Priority Queue");
		} else if(q.equals("htb")) {
			panelOptions.add(panelHtb);
			lblQdiscName.setText("Hierarchy Token Bucket");
		}
	}
	
	public boolean apply() {
		String q = listQDisc.getItemText(listQDisc.getSelectedIndex());
		if(q.equals("pfifo_fast")) {
			targetQdisc.setQdisc(q);
			targetQdisc.setOptions("");
		} else if(q.equals("pfifo")) {
			String opts = "";
			Double d = null;
			d = dblbxPfifoLimit.getValue();
			if(d != null) opts += "limit " + d.doubleValue();
			targetQdisc.setQdisc(q);
			targetQdisc.setOptions(opts);
		} else if(q.equals("bfifo")) {
			String opts = "";
			Double d = null;
			d = dblbxBfifoLimit.getValue();
			if(d != null) opts += "limit " + d.doubleValue();
			targetQdisc.setQdisc(q);
			targetQdisc.setOptions(opts);
		} else if(q.equals("sfq")) {
			String opts = "";
			Double d = null;
			Integer v = null;
			v = intbxSfqLimit.getValue();
			if(v != null) {
				int limit = v.intValue();
				if(limit < 2) limit = 2;
				opts += "limit " + limit + " ";
			}
			v = intbxSfqPerturb.getValue();
			if(v != null) opts += "perturb " + v.intValue() + " ";
			d = dblbxSfqQuantum.getValue();
			if(d != null) opts += "quantum " + d.doubleValue() + " ";
			targetQdisc.setQdisc(q);
			targetQdisc.setOptions(opts);
		} else if(q.equals("tbf")) {
			String opts = "";
			Double d = null;
			d = dblbxTbfRate.getValue();
			if(d != null) {
				double rate = d.doubleValue();
				if(rate < 1) rate = 1;
				opts += "rate " + rate + "bps ";
			}
			else { Window.alert("Parameter 'rate' must have a valid value."); return false; }
			d = dblbxTbfBuffer.getValue();
			if(d != null) {
				double buffer = d.doubleValue();
				if(buffer < 8) buffer = 8;
				opts += "buffer " + buffer + " ";
			}
			else { Window.alert("Parameter 'buffer' must have a valid value."); return false; }
			if(rdbtnTbfLimit.getValue()) {
				d = dblbxTbfLimit.getValue();
				if(d != null) opts += "limit " + d.doubleValue() + " ";
				else { Window.alert("Parameter 'limit' must have a valid value."); return false; }
			} else if(rdbtnTbfLatency.getValue()) {
				d = dblbxTbfLatency.getValue();
				if(d != null) opts += "latency " + d.doubleValue() + " ";
				else { Window.alert("Parameter 'latency' must have a valid value."); return false; }
			} else {
				Window.alert("Either 'limit' or 'latency' is required.");
				return false;
			}
			d = dblbxTbfMpu.getValue();
			if(d != null) opts += "mpu " + d.doubleValue() + " ";
			targetQdisc.setQdisc(q);
			targetQdisc.setOptions(opts);
		} else if(q.equals("prio")) {
			String opts = "";
			Integer v = null;
			String s = null;
			v = intbxPrioBands.getValue();
			if(v != null) opts += "bands " + v.intValue() + " ";
			else opts += "";
			s = txtbxPrioMap.getValue().trim();
			if(!s.isEmpty()) {
				String[] u = s.trim().split(Values.REGEX_ALL_WHITESPACE);
				if(u.length == 16) opts += "priomap " + s.trim() + " "; 
			}
			targetQdisc.setQdisc(q);
			targetQdisc.setOptions(opts);
			targetQdisc.setContainer(targetQdisc.getContainer());
		} else if(q.equals("htb")) {
			String opts = "";
			String s = null;
			s = txtbxHtbDefault.getValue().trim();
			if(!s.isEmpty()) opts += "default " + s.trim() + " ";
			targetQdisc.setQdisc(q);
			targetQdisc.setOptions(opts);
		}
		return true;
	}

	public void setTargetNode(NodeQDisc nq) {
		if(nq == null) return;
		txtbxParent.setText(nq.getParentId());
		txtbxHandle.setText(nq.getHandle());
		String q = nq.getQdisc();
		int listItems = listQDisc.getItemCount();
		for(int i = 0; i < listItems; i++) {
			if(listQDisc.getItemText(i).equals(q)) {
				listQDisc.setSelectedIndex(i);
				break;
			}
		}
		
		setQDisc(q);
		
		String opts = nq.getOptions().trim();
		
		if(q.equals("pfifo_fast")) {
			
		} else if(q.equals("pfifo")) {
			String[] u = opts.trim().split(Values.REGEX_ALL_WHITESPACE);
			int idxLimit = -1;
			for(int i = 0; i < u.length; i++) {
				if(u[i].equals("limit")) idxLimit = i;
			}
			Double value = getBaseDouble(u[idxLimit + 1]);
			if(value != null) dblbxPfifoLimit.setValue(value);
			else dblbxBfifoLimit.setText("");
		} else if(q.equals("bfifo")) {
			String[] u = opts.trim().split(Values.REGEX_ALL_WHITESPACE);
			int idxLimit = -1;
			for(int i = 0; i < u.length; i++) {
				if(u[i].equals("limit")) idxLimit = i;
			}
			Double value = getBaseDouble(u[idxLimit + 1]);
			if(value != null) dblbxBfifoLimit.setValue(value);
			else dblbxBfifoLimit.setText("");
		} else if(q.equals("sfq")) {
			String[] u = opts.trim().split(Values.REGEX_ALL_WHITESPACE);
			int idxLimit = -1;
			int idxPerturb = -1;
			int idxQuantum = -1;
			for(int i = 0; i < u.length; i++) {
				if(u[i].equals("limit")) {
					idxLimit  = i;
				} else if(u[i].equals("perturb")) {
					idxPerturb = i;
				} else if(u[i].equals("quantum" )) {
					idxQuantum = i;
				}
			}
			if(idxLimit != -1) {
				Double d = getBaseDouble(u[idxLimit + 1]);
				if(d == null) intbxSfqLimit.setText("");
				else intbxSfqLimit.setValue(d.intValue());
			}
			if(idxPerturb != -1) {
				Double d = getBaseDouble(u[idxPerturb + 1]);
				if(d == null) intbxSfqPerturb.setText("");
				else intbxSfqPerturb.setValue(d.intValue());
			}
			if(idxQuantum != -1) {
				Double d = getBaseDouble(u[idxQuantum + 1]);
				if(d == null) dblbxSfqQuantum.setText("");
				else dblbxSfqQuantum.setValue(d);
			}
		} else if(q.equals("tbf")) {
			String[] u = opts.trim().split(Values.REGEX_ALL_WHITESPACE);
			int idxRate = -1, idxBuffer = -1, idxLimit = -1, idxLatency = -1, idxMpu = -1;
			for(int i = 0; i < u.length; i++) {
				if(u[i].equals("limit")) {
					idxLimit  = i;
				} else if(u[i].equals("buffer") || u[i].equals("burst") || u[i].equals("maxburst")) {
					idxBuffer = i;
				} else if(u[i].equals("rate" )) {
					idxRate = i; 
				} else if(u[i].equals("latency")) {
					idxLatency = i;
				} else if(u[i].equals("mpu")) {
					idxMpu = i;
				}
			}
			if(idxLimit != -1) {
				if(idxLimit > idxLatency) {
					dblbxTbfLimit.setValue(getBaseDouble(u[idxLimit + 1]));
					rdbtnTbfLimit.setValue(true);
				}
			}
			if(idxLatency != -1) {
				if(idxLatency > idxLimit) {
					dblbxTbfLatency.setValue(getBaseDouble(u[idxLatency + 1]));
					rdbtnTbfLatency.setValue(true);
				}
			}
			if(idxRate != -1) {
				dblbxTbfRate.setValue(getBaseDouble(u[idxRate + 1]));
			}
			if(idxBuffer != -1) {
				dblbxTbfBuffer.setValue(getBaseDouble(u[idxBuffer + 1]));
			}
			if(idxMpu != -1) {
				dblbxTbfMpu.setValue(getBaseDouble(u[idxMpu + 1]));
			}
		} else if(q.equals("prio")) {
			String[] u = opts.trim().split(Values.REGEX_ALL_WHITESPACE);
			int idxBands = -1, idxMap = -1;
			for(int i = 0; i < u.length; i++) {
				if(u[i].equals("bands")) idxBands = i;
				if(u[i].equals("priomap")) idxMap = i;
			}
			if(idxBands != -1) {
				Double d = getBaseDouble(u[idxBands + 1]);
				if(d != null) {
					Integer v = d.intValue();
					intbxPrioBands.setValue(v);
				}
			}
			if(idxMap != -1) {
				String temp = "";
				int j = 0;
				for(int i = idxMap + 1; i < u.length; i++) {
					temp += u[i] + " ";
					j++;
				}
				if(j != 16) return;
				txtbxPrioMap.setText(temp);
			}
		} else if(q.equals("htb")) {
			String[] u = opts.trim().split(Values.REGEX_ALL_WHITESPACE);
			int idxDefault = -1;
			for(int i = 0; i < u.length; i++) {
				if(u[i].equals("default")) idxDefault = i;
			}
			if(idxDefault != -1) {
				txtbxHtbDefault.setText(u[idxDefault + 1]);
			}
		}
		this.targetQdisc = nq;
	}
	
	public static Double getBaseDouble(String value) {
		Double retval = null;
		value = value.trim().toLowerCase();
		int trunc = -1; double multiplier = 1.0;
		if(value.endsWith("k") || value.endsWith("kb") || value.endsWith("kbps")) {
			trunc = value.indexOf("k");
			multiplier = 1000.0;
		} else if(value.endsWith("m") || value.endsWith("mb") || value.endsWith("mbps")) {
			trunc = value.indexOf("m");
			multiplier = 1000000.0;
		} else if(value.endsWith("kbit")) {
			trunc = value.indexOf("kbit");
			multiplier = 1000.0 / 8.0;
		} else if(value.endsWith("mbit")) {
			trunc = value.indexOf("mbit");
			multiplier = 1000000.0 / 8.0;
		} else if(value.endsWith("b") || value.endsWith("bps")) {
			trunc = value.indexOf("b");
			multiplier = 1.0;
		} else if(value.endsWith("us") || value.endsWith("usec") || value.endsWith("usecs")) {
			trunc = value.indexOf("u");
			multiplier = 1.0;
		} else if(value.endsWith("ms") || value.endsWith("msec") || value.endsWith("msecs")) {
			trunc = value.indexOf("m");
			multiplier = 1000.0;
		} else if(value.endsWith("s") || value.endsWith("sec") || value.endsWith("secs")) {
			trunc = value.indexOf("s");
			multiplier = 1000000.0;
		}

		if(trunc > 0) {
			value = value.substring(0, trunc);
		}
		
		try {
			retval = new Double(Double.parseDouble(value) * multiplier);
		} catch (Throwable t) {
			Utilities.failureAlert("NodeQDiscEditor.getBaseDouble(...) #560", "Double.pasreDouble failure", t);
			retval = null;
		}

		return retval;
	}

}

/*

UNITS
       All parameters accept a floating point number, possibly followed  by  a
       unit.

       Bandwidths or rates can be specified in:

       kbps   Kilobytes per second

       mbps   Megabytes per second

       kbit   Kilobits per second

       mbit   Megabits per second

       bps or a bare number
              Bytes per second

       Amounts of data can be specified in:

       kb or k
              Kilobytes

       mb or m
              Megabytes

       mbit   Megabits

       kbit   Kilobits

       b or a bare number
              Bytes.

       Lengths of time can be specified in:

       s, sec or secs
              Whole seconds

       ms, msec or msecs
              Milliseconds

       us, usec, usecs or a bare number
              Microseconds.

*/